package com.bmps.springcloud.insuredmicroservice.service;

import com.bmps.springcloud.common.utils.DateUtil;
import com.bmps.springcloud.insuredmicroservice.constant.accept.AcceptConstant;
import com.bmps.springcloud.insuredmicroservice.constant.accept.MultConstant;
import com.bmps.springcloud.insuredmicroservice.constant.accept.QuotaConstant;
import com.bmps.springcloud.insuredmicroservice.model.accept.resquest.AppntModel;
import com.bmps.springcloud.insuredmicroservice.model.accept.resquest.InsurantModel;
import com.bmps.springcloud.insuredmicroservice.model.accept.resquest.ProductModel;
import com.bmps.springcloud.insuredmicroservice.model.trail.TrialPersonModel;
import com.bmps.springcloud.insuredmicroservice.repository.accept.master.SysRedisRepository;
import com.bmps.springcloud.insuredmicroservice.util.accept.AcceptUtil;
import com.bmps.springcloud.insuredmicroservice.util.trial.TrialUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author lyw
 * @date 2019/9/3 10:53
 */
@Service
public class RedisServiceImpl implements RedisService {
    private static Logger logger = LoggerFactory.getLogger(RedisServiceImpl.class);

    private final RedisTemplate redisTemplate;
    private final SysRedisRepository sysRedisRepository;
    private final RedissonClient redissonClient;

    public RedisServiceImpl(RedisTemplate redisTemplate, SysRedisRepository sysRedisRepository, RedissonClient redissonClient) {
        this.redisTemplate = redisTemplate;
        this.sysRedisRepository = sysRedisRepository;
        this.redissonClient = redissonClient;
    }

    @Override
    public Object get(String key) {
        try {
            return redisTemplate.opsForValue().get(key);
        } catch (Exception e) {
            logger.error("请求redis服务发生异常： " + e.getMessage() + "! key为：" + key);
            return null;
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public Set<?> members(String key) throws Exception {
        /*
         * 如果redis异常，黑白单获取应该扔出异常，而不是catch
         */
        return redisTemplate.opsForSet().members(key);
    }

    @Override
    @SuppressWarnings("unchecked")
    public void set(String key, String value, long outOfTime, TimeUnit timeUnit) {
        try {
            redisTemplate.opsForValue().set(key, value, outOfTime, timeUnit);
        } catch (Exception e) {
            logger.error("设置redis服务发生异常： " + e.getMessage() + "! key为:" + key);
        }
    }

    @Override
    public boolean isMainRisk(String riskCode) {
        String key = AcceptConstant.REDIS_PREFIX_R004 + "|" + riskCode;
        //先查询缓存
        Object object = null;
        try {
            object = redisTemplate.opsForValue().get(key);
        } catch (Exception e) {
            logger.error("查询缓存异常key=" + key, e.getMessage());
        }
        String value = null;
        //缓存不存在查数据库
        if (object == null && (value = sysRedisRepository.query(key)) == null) {
            return false;
        } else {
            return (object == null ? value : object.toString()).endsWith(AcceptConstant.MAIN_RISK_FLAG);
        }
    }

    @Override
    public BigDecimal getCacheQuota(String sort, String id, BigDecimal currentAmount, String contNo) {
        BigDecimal bigDecimal = BigDecimal.valueOf(0);
        //锁key  QUOTA|证件号|类别
        RLock lock = redissonClient.getLock(QuotaConstant.QUOTA + QuotaConstant.V_LINE + id + QuotaConstant.V_LINE + sort);
        boolean locked = false;
        //当前类别需要的key QUOTA|类别|证件号|保单号|QUOTA
        String keyCurrent = QuotaConstant.QUOTA + QuotaConstant.V_LINE + sort + QuotaConstant.V_LINE + id + QuotaConstant.V_LINE + contNo + QuotaConstant.V_LINE + QuotaConstant.QUOTA;
        //模糊匹配key QUOTA|类别|证件号*
        String keyDim = QuotaConstant.QUOTA + QuotaConstant.V_LINE + sort + QuotaConstant.V_LINE + id + "*";
        try {
            locked = lock.tryLock(2000, 2000, TimeUnit.MILLISECONDS);
            if (locked) {
                Set<String> set = redisTemplate.keys(keyDim);
                if (CollectionUtils.isNotEmpty(set)) {
                    //过滤可能存在的当前单
                    Set<String> newSet = set.stream().filter(x -> !x.contains(contNo)).collect(Collectors.toSet());
                    if (CollectionUtils.isNotEmpty(newSet)) {
                        //查询缓存的当前类别风险保额
                        List<BigDecimal> amounts = redisTemplate.opsForValue().multiGet(newSet);
                        if (CollectionUtils.isNotEmpty(amounts)) {
                            for (BigDecimal amount : amounts) {
                                bigDecimal = bigDecimal.add(amount);
                            }
                        }
                    }
                    //当前单放入缓存 存在时间10分钟
                    if (!set.contains(keyCurrent)) {
                        redisTemplate.opsForValue().set(keyCurrent, currentAmount, 10, TimeUnit.MINUTES);
                    }
                } else {
                    redisTemplate.opsForValue().set(keyCurrent, currentAmount, 10, TimeUnit.MINUTES);
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (locked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        return bigDecimal;
    }

    @Override
    public void delDim(String key) {
        try {
            Set<String> set = redisTemplate.keys(key);
            if (!CollectionUtils.isEmpty(set)) {
                redisTemplate.delete(set);
            }
        } catch (Exception e) {
            logger.error("模糊删除缓存异常", e);
            e.printStackTrace();
        }
    }

    @Override
    public Integer getCacheMult(String type, Integer planVersion, String id, Integer mult, String contNo) {

        Integer planVersionMults = 0;
        //锁key  QUOTA|证件号|类别
        RLock lock = redissonClient.getLock(MultConstant.MULT + MultConstant.V_LINE + id + QuotaConstant.V_LINE + type);
        boolean locked = false;
        //当前类别需要的key MULT|类别|证件号|保单号|版本计划|MULT
        String keyCurrent = MultConstant.MULT + MultConstant.V_LINE + type + MultConstant.V_LINE + id + QuotaConstant.V_LINE + contNo + MultConstant.V_LINE + planVersion + MultConstant.V_LINE + MultConstant.MULT;
        //模糊匹配key MULT|类别|证件号*
        String keyDim = MultConstant.MULT + MultConstant.V_LINE + type + MultConstant.V_LINE + id + "*";
        try {
            locked = lock.tryLock(2000, 2000, TimeUnit.MILLISECONDS);
            if (locked) {
                Set<String> set = redisTemplate.keys(keyDim);
                if (CollectionUtils.isNotEmpty(set)) {
                    //过滤可能存在的当前单
                    Set<String> newSet = set.stream().filter(x -> !x.contains(contNo)).collect(Collectors.toSet());
                    if (CollectionUtils.isNotEmpty(newSet)) {
                        //如果当前计划和缓存计划不一致返回-1
                        for (String caachKey : newSet) {
                            String[] param = caachKey.split("\\|");
                            if (!(planVersion.toString()).equals(param[4])) {
                                return -1;
                            }
                        }
                        // 累计缓存中的份数
                        List<Integer> mults = redisTemplate.opsForValue().multiGet(newSet);
                        if (CollectionUtils.isNotEmpty(mults)) {
                            for (Integer caachMult : mults) {
                                planVersionMults += caachMult;
                            }
                        }
                    }
                    //当前单放入缓存 存在时间10分钟
                    if (!set.contains(keyCurrent)) {
                        redisTemplate.opsForValue().set(keyCurrent, mult, 10, TimeUnit.MINUTES);
                    }
                } else {
                    redisTemplate.opsForValue().set(keyCurrent, mult, 10, TimeUnit.MINUTES);
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (locked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        return planVersionMults;
    }

    @Override
    public boolean exemptionProduct(String riskCode) {
        Object obj = redisTemplate.opsForValue().get(AcceptConstant.REDIS_PREFIX_R004 + "|" + riskCode);
        if (obj != null) {
            String[] result = String.valueOf(obj).split("\\|");
            return (StringUtils.equals(result[4], "1") || StringUtils.equals(result[4], "2"));
        }
        return false;
    }

    @Override
    public boolean isExemptDuty(String riskCode, AppntModel appntModel, InsurantModel insurantModel, Date polApplyDate) {
        //豁免责任判断
        if (AcceptUtil.isSamePerson(insurantModel, appntModel)) {
            return false;
        }
        long age = DateUtil.getYears(appntModel.getAppntBirthday(), polApplyDate);
        Object object = redisTemplate.opsForValue().get(AcceptConstant.EXEMPT_DUTY_RISK + riskCode);
        return object != null && age < (int) object;
    }

    @Override
    public TrialPersonModel getCalculateMode(TrialPersonModel trialPersonModel, String mode) {
        String riskCode = trialPersonModel.getRiskCode();
        StringBuilder sb = new StringBuilder(16);
        sb.append(AcceptConstant.REDIS_PREFIX_R004).append("|").append(riskCode);
        Object object = get(sb.toString());
        if (object == null) {
            return new TrialPersonModel();
        }
        TrialUtil.setSpecialElement(trialPersonModel, object.toString());

        sb.setLength(0);
        sb.append(AcceptConstant.REDIS_PREFIX_R006).append("|").append(trialPersonModel.getRiskCode()).
                append("|").append(mode);
        object = get(sb.toString());
        if (object == null) {
            return new TrialPersonModel();
        }
        trialPersonModel.setFormula(object.toString());
        logger.info("【" + riskCode + "】【公式】【KEY】" + sb.toString());

        sb.setLength(0);
        sb.append(AcceptConstant.REDIS_PREFIX_R001).append("|").append(riskCode);
        logger.info("【" + riskCode + "】【产品计算要素配置】【KEY】" + sb.toString());
        object = get(sb.toString());
        if (object == null) {
            return new TrialPersonModel();
        }
        String result = TrialUtil.getRateKey(trialPersonModel, object.toString(), mode);
        object = get(result);
        if (object == null) {
            return new TrialPersonModel();
        }
        TrialUtil.setElementRate(trialPersonModel, object.toString());

        sb.setLength(0);
        sb.append(AcceptConstant.REDIS_PREFIX_R005).append("|").append(riskCode).
                append("|").append(mode);
        object = get(sb.toString());
        if (object == null) {
            return new TrialPersonModel();
        }
        logger.info("【" + riskCode + "】【产品计算公式要素配置】【KEY】" + sb.toString());
        TrialUtil.setElementMapping(trialPersonModel, object.toString());
        return trialPersonModel;
    }

    @Override
    public TrialPersonModel getCalculateModeNew(TrialPersonModel trialPersonModel, String mode) {
        String riskCode = trialPersonModel.getRiskCode();
        StringBuilder sb = new StringBuilder(16);
        sb.append(AcceptConstant.REDIS_PREFIX_R004).append("|").append(riskCode);
        Object object = get(sb.toString());
        if (object == null) {
            return new TrialPersonModel();
        }
        TrialUtil.setSpecialElement(trialPersonModel, object.toString());

        sb.setLength(0);
        sb.append(AcceptConstant.REDIS_PREFIX_R006).append("|").append(trialPersonModel.getRiskCode()).
                append("|").append(mode);
        object = get(sb.toString());
        if (object == null) {
            return new TrialPersonModel();
        }
        trialPersonModel.setFormula(object.toString());
        logger.info("【" + riskCode + "】【公式】【KEY】" + sb.toString());

        sb.setLength(0);
        sb.append(AcceptConstant.REDIS_PREFIX_R001).append("|").append(riskCode).append("|").append(mode);
        logger.info("【" + riskCode + "】【产品计算要素配置】【KEY】" + sb.toString());
        object = get(sb.toString());
        if (object == null) {
            return new TrialPersonModel();
        }
        String result = TrialUtil.getRateKey(trialPersonModel, object.toString(), mode);
        logger.info("R003" + result);
        object = get(result);
        if (object == null) {
            return new TrialPersonModel();
        }
        TrialUtil.setElementRate(trialPersonModel, object.toString());

        sb.setLength(0);
        sb.append(AcceptConstant.REDIS_PREFIX_R005).append("|").append(riskCode).
                append("|").append(mode);
        object = get(sb.toString());
        if (object == null) {
            return new TrialPersonModel();
        }
        logger.info("【" + riskCode + "】【产品计算公式要素配置】【KEY】" + sb.toString());
        TrialUtil.setElementMapping(trialPersonModel, object.toString());
        return trialPersonModel;
    }

    @Override
    public TrialPersonModel getCalculateModeComplex(TrialPersonModel trialPersonModel, String mode) {
        String riskCode = trialPersonModel.getRiskCode();
        StringBuilder sb = new StringBuilder(16);
        sb.append(AcceptConstant.REDIS_PREFIX_R006).append("|").append(trialPersonModel.getRiskCode()).
                append("|").append(mode);
        Object object = get(sb.toString());
        if (object == null) {
            return new TrialPersonModel();
        }
        trialPersonModel.setFormula(object.toString());
        logger.info("【" + riskCode + "】【公式】【KEY】" + sb.toString());

        sb.setLength(0);
        sb.append(AcceptConstant.REDIS_PREFIX_R001).append("|").append(riskCode).append("|").append(mode);
        logger.info("【" + riskCode + "】【产品计算要素配置】【KEY】" + sb.toString());
        object = get(sb.toString());
        if (object == null) {
            return new TrialPersonModel();
        }
        String result = TrialUtil.getRateKey(trialPersonModel, object.toString(), mode);
        object = get(result);
        if (object == null) {
            return new TrialPersonModel();
        }
        TrialUtil.setElementRate(trialPersonModel, object.toString());

        sb.setLength(0);
        sb.append(AcceptConstant.REDIS_PREFIX_R005).append("|").append(riskCode).
                append("|").append(mode);
        object = get(sb.toString());
        if (object == null) {
            return new TrialPersonModel();
        }
        logger.info("【" + riskCode + "】【产品计算公式要素配置】【KEY】" + sb.toString());
        TrialUtil.setElementMapping(trialPersonModel, object.toString());
        return trialPersonModel;
    }

    @Override
    public Integer getCity(String city) {
        Integer code = 3;
        if (StringUtils.isNotBlank(city)) {
            StringBuilder sb = new StringBuilder(16);
            sb.append(AcceptConstant.REDIS_PREFIX_CITY).append(AcceptConstant.VERTICAL_BAR).append(city);
            code = (Integer) get(sb.toString());
            if (null == code) {
                code = 3;
            }
        }
        return code;
    }

    @Override
    public boolean isMember(String key, String value) {
        Boolean member = redisTemplate.opsForSet().isMember(key, value);
        return member != null && member.booleanValue();
    }

    @Override
    public boolean isCloud(String agencyCode, String riskCode) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(AcceptConstant.REDIS_PREFIX_B001).append("10").append(AcceptConstant.VERTICAL_BAR).
                append(agencyCode);
        Object obj = redisTemplate.opsForValue().get(stringBuilder.toString());
        if (obj != null) {
            return true;
        }
        stringBuilder.setLength(0);
        stringBuilder.append(AcceptConstant.REDIS_PREFIX_B002).append("10").append(AcceptConstant.VERTICAL_BAR).
                append(agencyCode).append(AcceptConstant.VERTICAL_BAR).append(riskCode);
        obj = redisTemplate.opsForValue().get(stringBuilder.toString());
        if (obj != null) {
            return true;
        }

        return false;
    }
}
